﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

using SimpleIR;
using PInfoModel;

namespace PIPostAnalysis.UserQuery
{
	/// <summary>
	/// This class contains the methods needed to compute the parameter info we are interested in.
	/// </summary>
	public static class ParameterInfoCompute
	{
		private static Dictionary<IRInvokeIdentity, ParameterInfo> PInfoMap;

		public static Dictionary<IRInvokeIdentity, ParameterInfo> ComputePInfo(CompleteAnalysisInfoForProgram pip)
		{
			PInfoMap = new Dictionary<IRInvokeIdentity, ParameterInfo>();

			AnalyzeProgramInit(pip);
			UseModWorkList(pip);
			Dictionary<IRInvokeIdentity, ParameterInfo> res = PInfoMap;
			PInfoMap = null;

			return res;
		}

		//////////////////////////////////////////////////////////////
		//Do the fixpoint on the call graph

		private static void UseModWorkList(CompleteAnalysisInfoForProgram pip)
		{
			CallGraph cg = pip.Program.CallGraph;
			List<CallGraphNode> reachnodes = cg.GetNodeListInTopoOrder();
			reachnodes.Reverse();
			Queue<CallGraphNode> wlist = new Queue<CallGraphNode>(reachnodes);

			while(wlist.Count != 0)
			{
				CallGraphNode pending = wlist.Dequeue();
				if(!PInfoMap.ContainsKey(pending.Def))
					continue;

				ParameterInfo callerinfo = PInfoMap[pending.Def];
				bool changed = false;

				foreach(ParamInfoCallSite pics in callerinfo.CallSiteInfoForCallsInMethod)
				{
					ParameterInfo calleeinfo = PInfoMap[pics.CalleeInv];

					foreach(SingleParamInfo spi in callerinfo.PNameDataMap)
						changed |= ProcessSingleParameter(spi, pics, calleeinfo);
				}

				if(changed)
				{
					foreach(CallGraphEdge cge in pending.CallInList)
					{
						CallGraphNode pnode = cg.GetCallNodeForInvokeIdentity(cge.CallerIdentity);
						if(!wlist.Contains(pnode))
							wlist.Enqueue(pnode);
					}
				}
			}
		}

		private static bool ProcessSingleParameter(SingleParamInfo spi, ParamInfoCallSite pics, ParameterInfo calleeinfo)
		{
			bool changed = false;

			foreach(MemLocID spilid in spi.ReachableLocsAtSomePoint)
			{
				foreach(KeyValuePair<MemLocID, HashSet<MemLocID>> rmm in pics.MemLocsPassedInMap)
				{
					if(rmm.Value.Contains(spilid))
					{
						IEnumerable<Tuple<IRType, MemLocWPath, IRSourceLocation>> usesff = calleeinfo.PNameDataMap.Where((SingleParamInfo spiii) => spiii.ReachableLocsAtSomePoint.Contains(rmm.Key)).SelectMany((SingleParamInfo spiii) => spiii.UsedLocations);
						HashSet<Tuple<IRType, IRBasicAccessPath>> usepaths = new HashSet<Tuple<IRType, IRBasicAccessPath>>(usesff.Where((Tuple<IRType, MemLocWPath, IRSourceLocation> tt) => tt.Item2.MemLocation == rmm.Key).Select((Tuple<IRType, MemLocWPath, IRSourceLocation> tt) => new Tuple<IRType, IRBasicAccessPath>(tt.Item1, tt.Item2.AccPath)));
						foreach(Tuple<IRType, IRBasicAccessPath> tbap in usepaths)
						{
							Tuple<IRType, MemLocWPath, IRSourceLocation> ntp = new Tuple<IRType, MemLocWPath, IRSourceLocation>(tbap.Item1, MemLocWPath.MakeMemLocWithPath(spilid, tbap.Item2), pics.CallLocation);
							changed |= (!spi.UsedLocations.Contains(ntp));
							spi.UsedLocations.Add(ntp);
						}

						IEnumerable<Tuple<IRType, MemLocWPath, IRSourceLocation>> modsff = calleeinfo.PNameDataMap.Where((SingleParamInfo spiii) => spiii.ReachableLocsAtSomePoint.Contains(rmm.Key)).SelectMany((SingleParamInfo spiii) => spiii.ModLocations);
						HashSet<Tuple<IRType, IRBasicAccessPath>> modpaths = new HashSet<Tuple<IRType, IRBasicAccessPath>>(modsff.Where((Tuple<IRType, MemLocWPath, IRSourceLocation> tt) => tt.Item2.MemLocation == rmm.Key).Select((Tuple<IRType, MemLocWPath, IRSourceLocation> tt) => new Tuple<IRType, IRBasicAccessPath>(tt.Item1,  tt.Item2.AccPath)));
						foreach(Tuple<IRType, IRBasicAccessPath> tbap in modpaths)
						{
							Tuple<IRType, MemLocWPath, IRSourceLocation> ntp = new Tuple<IRType, MemLocWPath, IRSourceLocation>(tbap.Item1, MemLocWPath.MakeMemLocWithPath(spilid, tbap.Item2), pics.CallLocation);
							changed |= (!spi.ModLocations.Contains(ntp));
							spi.ModLocations.Add(ntp);
						}
					}
				}
			}

			return changed;
		}

		//////////////////////////////////////////////////////////////
		//Do all the initial use/mod call computation for the methods

		private static void AnalyzeProgramInit(CompleteAnalysisInfoForProgram pip)
		{
			SingleParamInfo.CPInfo = pip;
			SingleParamInfo.PProg = pip.Program;

			foreach(CompleteAnalysisInfoMethod cmm in pip.MethodInfo.Values)
				AnalyzeMethod(pip.Program.MethodBodyTable.GetBodyForInvokeExactSigMatch(cmm.InvokeID), cmm);

			SingleParamInfo.CPInfo = null;
			SingleParamInfo.PProg = null;
		}

		private static void AnalyzeMethod(IRBodyDef bdf, CompleteAnalysisInfoMethod cmm)
		{
			ParameterInfo pinfo = new ParameterInfo(bdf.InvokeID);
			PInfoMap.Add(bdf.InvokeID, pinfo);
			
			foreach(IRRootIdentifier rr in cmm.SummaryInState.NameToNodeMap.Keys)
				pinfo.PNameDataMap.Add(new SingleParamInfo(rr));
			pinfo.PNameDataMap.Sort((SingleParamInfo spi1, SingleParamInfo spi2) => spi1.ParameterName.RootNameString().CompareTo(spi2.ParameterName.RootNameString()));

			foreach(CompleteAnalysisInfoMethodContext cmmc in cmm.AllCallContexts)
			{
				Dictionary<int, ProgramPointInfo> ppimap = cmmc.ProgramPointToPointInfoMap();
				foreach(ProgramPointInfo ppi in ppimap.Values)
				{
					if(ppi.PreOperationModel == null)
						continue;

					foreach(SingleParamInfo spi in pinfo.PNameDataMap)
					{
						HashSet<PIMNode> reachset = ppi.PreOperationModel.GetAllReachableNodesFromRoot(spi.ParameterName);
						reachset.Add(ppi.PreOperationModel.ResolveHomeForRoot(spi.ParameterName));

						foreach(PIMNode nn in reachset)
							PIModel.AddAllLocationIDSForNodeToSet(nn, spi.ReachableLocsAtSomePoint);
					}
				}
			}

			foreach(CompleteAnalysisInfoMethodContext cmmc in cmm.AllCallContexts)
				AnalyzeContext(bdf, cmmc, pinfo);
		}

		private static void AnalyzeContext(IRBodyDef bdf, CompleteAnalysisInfoMethodContext cmmc, ParameterInfo m)
		{
			SingleParamInfo.CPInfoCTX = cmmc;

			foreach(IRBasicBlock bb in bdf.Body.BasicBlockList)
			{
				foreach(IRByteCode bc in bb.Operations)
					AnalyzeStmt(cmmc, bc, m);
			}

			SingleParamInfo.CPInfoCTX = null;
		}

		private static void AnalyzeStmt(CompleteAnalysisInfoMethodContext cmmc, IRByteCode bc, ParameterInfo m)
		{
			Dictionary<int, ProgramPointInfo> ppi = cmmc.ProgramPointToPointInfoMap();

			//if the line is never executed then don't worry
			if(!ppi.ContainsKey(bc.UseModLoc) || ppi[bc.UseModLoc].PreOperationModel == null)
				return;

			PIModel prem = ppi[bc.UseModLoc].PreOperationModel;
			foreach(SingleParamInfo spi in m.PNameDataMap)
			{
				switch(bc.OperationCode)
				{
					case OpCode.Op.Call:
					case OpCode.Op.CallVirt:
						spi.AnalyzeCall((IRCallOp)bc, m.CallSiteInfoForCallsInMethod);
						break;
					case OpCode.Op.AllocCons:
						spi.AnalyzeAlloc((IRAllocationOp)bc, m.CallSiteInfoForCallsInMethod);
						break;
					case OpCode.Op.LoadFromLocal:
						spi.AnalyzeLoadFromLocal((IRLoadToEvalStackBasic)bc, prem);
						break;
					case OpCode.Op.StoreToLocal:
						spi.AnalyzeStoreToLocal((IRStoreFromEvalStackBasic)bc, prem);
						break;
					case OpCode.Op.LoadFromGlobal:
						spi.AnalyzeLoadFromGlobal((IRLoadToEvalStackBasic)bc, prem);
						break;
					case OpCode.Op.StoreToGlobal:
						spi.AnalyzeStoreToGlobal((IRStoreFromEvalStackBasic)bc, prem);
						break;
					case OpCode.Op.ArrayLoad:
						spi.AnalyzeArrayLoad((IRLoadToEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.ArrayStore:
						spi.AnalyzeArrayStore((IRStoreFromEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.ArrayLength:
						spi.AnalyzeArrayLength(bc, prem);
						break;
					case OpCode.Op.IndirectRead:
						spi.AnalyzeIndirectRead((IRLoadToEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.IndirectWrite:
						spi.AnalyzeIndirectWrite((IRStoreFromEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.LoadMemberField:
						spi.AnalyzeLoadMemberField((IRLoadToEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.StoreMemberField:
						spi.AnalyzeStoreMemberField((IRStoreFromEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.IndirectInitialize:
						spi.AnalyzeIndirectInitialize((IRStoreFromEvalStackIndirect)bc, prem);
						break;
					case OpCode.Op.UnBox:
						spi.AnalyzeUnbox((IREvalStackTypeOp)bc, prem);
						break;
					case OpCode.Op.Cast:
						spi.AnalyzeCast(bc, prem);
						break;
					case OpCode.Op.IsInst:
						spi.AnalyzeIsInst(bc, prem);
						break;
					case OpCode.Op.Constrain:
						spi.AnalyzeConstrain(((IREvalStackTypeOp)bc).TypeArg, ((IREvalStackTypeOp)bc).ConstrainPos, bc, prem);
						break;
					default:
						break;
				}
			}
		}
	}
}
